/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package org.javaongems.server.rpc.io;

import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.FileItem;
import org.apache.log4j.Logger;
import org.javaongems.client.service.HttpPostRpc;
import org.javaongems.runtime.lang.StringUtils;
import org.javaongems.server.GemServe;

import com.google.gwt.user.client.rpc.SerializableException;

/**
 * <p>Provides a basic template for handling posted form data using the 
 * HttpPostForm component.</p>
 * <p>Responsibilities:
 * <ul>
 * 	<li> support a configurable timeout period when waiting for the payload
 * 	<li> fetch the form payload from the http session for the specified order no
 * 	<li> provide a default handler for querying the posted information. note, 
 * 		this handler is intended to be overridden
 * 	<li> notify the keeper that the service has completed 
 * </ul>
 * </p>
 * <p>Collaborators:
 * <ul>
 * 	<li> {@link HttpPostGateKeeperAction} - which recieves, parses and keeps the posted data
 * </ul>
 * </p>
 * <p>Constraints:
 * <ul>
 * 	<li> Uses Java wait/notify inter-thread communication on the payload object. 
 * 	This will not work in a clustered J2EE environment. 
 * </ul>
 * </p>
 * 
 * TODO: Abstract this implementation and let the pub/sub behavior be pluggable.
 * @author Ashin Wimalajeewa (ash)
 */
public class BaseHttpPostPickupRpcImpl implements HttpPostRpc, GemServe.ServletConfigSupport {
	final static public String PICKUP_TIMEOUT_PARAM = "gems.io.httppost.pickup-timeout-param"; 
	final static public String PROGRESS_TIMEOUT_PARAM = "gems.io.httppost.progress-timeout-param"; 
	final static public int SLEEP_TIME = 1000;	// 1 sec
	final static private Logger LOGGER = Logger.getLogger(BaseHttpPostPickupRpcImpl.class);
	
	private long pickupTimeout = 1000 * 60 * 5;	// 5 min
	private long progressTimeout = 1000 * 3; // 3 sec 

	/**
	 * Reads the value of "gems.io.httppost.pickup-timeout-param" from the servlet 
	 * context's init parameters and assign that time-out value if assigned.
	 * @see org.javaongems.server.GemServe.ServletConfigSupport#setServletConfig(ServletConfig)
	 */
	public void setServletConfig(ServletConfig ctx) {
		String timeOutAsStr = ctx.getInitParameter(PICKUP_TIMEOUT_PARAM);
		if (!StringUtils.isEmpty(timeOutAsStr))
			pickupTimeout = Long.parseLong(timeOutAsStr);
		timeOutAsStr = ctx.getInitParameter(PROGRESS_TIMEOUT_PARAM);
		if (!StringUtils.isEmpty(timeOutAsStr))
			progressTimeout = Long.parseLong(timeOutAsStr);
	}
	
	/**
	 * Fetches the data from the http session and invokes the handler before
	 * finally notifying the keepeer of completion. This implementation will
	 * timeout after the pickupTimout duration and assume that the post has
	 * failed during the dispatch/uploading phase.
	 * @see org.javaongems.client.service.HttpPostRpc#pickupAndHandle(java.lang.String, java.lang.String)
	 */
	public void pickupAndHandle(String gemId, String orderNo) throws SerializableException {
		HttpServletRequest request = GemServe.getThreadLocalHttpServletRequest();
		HttpPostPayload payload = waitForPayloadToBeReadyForProcessing(request, gemId, orderNo);
		try {
			if (LOGGER.isDebugEnabled()) LOGGER.debug("handling [" + gemId + "," + orderNo + "]");
			if (payload == null)
				LOGGER.error("timed out with no payload");
			else
				handlePayload(payload);
			if (LOGGER.isDebugEnabled()) LOGGER.debug("completed handling");
		} finally {
			notifyKeeperOfCompletion(gemId, orderNo, payload);
			if (LOGGER.isDebugEnabled()) LOGGER.debug("unblocking post");
		}
	}

	/**
	 * Fetches the data from the http session and reports that progress back to
	 * the client. This implementation will timeout after the progressTimeout
	 * duration and assume that the post has either failed or completed.
	 * @see org.javaongems.client.service.HttpPostRpc#pingProgress(java.lang.String, java.lang.String)
	 */
	public ProgressInfo pingProgress(String gemId, String orderNo) {
		HttpServletRequest req = GemServe.getThreadLocalHttpServletRequest();
		ProgressInfo ret = null;
		if (LOGGER.isDebugEnabled()) LOGGER.debug("ping progress request received [" + gemId + "," + orderNo + "]");
		HttpPostPayload monitor = waitForPayloadToArrive(req, gemId, orderNo);
		if (monitor != null)
			ret = monitor.progress;
		if (LOGGER.isDebugEnabled()) LOGGER.debug("ping completed");
		return ret;
	}
	
	/**
	 * Waits for the gate keeper to publish the payload to the http session. This
	 * should not take long b/c an empty/unparsed payload reference is all that we
	 * are waiting for
	 * @param req - the http servlet request that this service method is executing under
	 * @param gemId - the gem id sent form the client
	 * @param orderNo - the order no sent from the client
	 * @return the payload that the keeper placed into the http session 
	 */
	protected HttpPostPayload waitForPayloadToArrive(HttpServletRequest req, String gemId, String orderNo) {
		HttpSession session = req.getSession();
		HttpPostPayload order;
		long start = System.currentTimeMillis();
		while (true) {
			order = HttpPostPayload.getFromSession(session, gemId, orderNo);
			long elapsed = System.currentTimeMillis() - start;
			if (LOGGER.isDebugEnabled()) LOGGER.debug("queried session o=" + order + ", elapsed=" + elapsed);
			if (order != null || elapsed > pickupTimeout)	// progressTimeout
				break;
			try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException err) {}
			if (LOGGER.isDebugEnabled()) LOGGER.debug("waiting for gate keeper");
		}
		return order; 
	}
	
	/**
	 * Uses a basic wait/notify approach to in form the keeper's thread to  
	 * unblock and return to the client.
	 * @param gemId - the gem id sent from the client
	 * @param orderNo - the order no sent form the client
	 * @param payload - the payload fetched from the session
	 */
	protected void notifyKeeperOfCompletion(String gemId, String orderNo, HttpPostPayload payload) {
		if (payload == null)
			return;
		synchronized (payload) {
			payload.notifyAll();	
		}
	}
	
	/**
	 * Waits for the payload from the http session for the given gem id and order no.
	 * @param req - the http servlet request that this service method is executing under
	 * @param gemId - the gem id sent form the client
	 * @param orderNo - the order no sent from the client
	 * @return the payload that the keeper placed into the http session 
	 */
	protected HttpPostPayload waitForPayloadToBeReadyForProcessing(HttpServletRequest req, String gemId, String orderNo) {
		HttpPostPayload order = null;
		HttpSession session = req.getSession();
		long start = System.currentTimeMillis();
		while (true) {
			order = HttpPostPayload.getFromSession(session, gemId, orderNo);
			boolean readyForProcessing = false;
			if (order != null) 
				readyForProcessing = order.progress.currentPhase == HttpPostRpc.ProgressInfo.PROCESSING_PHASE;
			long elapsed = System.currentTimeMillis() - start;
			if (LOGGER.isDebugEnabled()) LOGGER.debug("queried session o=" + order + ", elapsed=" + elapsed);
			if (readyForProcessing || elapsed > pickupTimeout)
				break;
			try { Thread.sleep(SLEEP_TIME); } catch (InterruptedException err) {}
			if (LOGGER.isDebugEnabled()) LOGGER.debug("waiting for gate keeper");
		}
		return order;
	}
	
	/**
	 * Default template method implementation to provide a sample of how to 
	 * access payload (ie. posted data) elements. 
	 * @param payload - the payload to handle
	 * @throws SerializableException
	 */
	protected void handlePayload(HttpPostPayload payload) throws SerializableException {
		LOGGER.info("Files>");
		for (int i = 0; i < payload.formFiles.length; i++) {
			FileItem fi = payload.formFiles[i];
			LOGGER.info( i + ". " + fi.getName() + ", " + fi.getSize());
		}
		LOGGER.info("Data>");
		LOGGER.info(payload.formData.toString());
	}
}
